Binder的权限控制

Binder的权限控制

在AOSP中存在大量的如下代码:

1
2
3
final long origId = Binder.clearCallingIdentity();
...
Binder.restoreCallingIdentity(origId);

这段代码到底有什么作用呢?它们总是成对的出现,如影随行,今天我们就来探究下里面的玄机。先透露下,实际上这段代码就是Binder的权限控制机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//frameworks/base/core/java/android/os/Binder.java
/**
* Reset the identity of the incoming IPC on the current thread. This can
* be useful if, while handling an incoming call, you will be calling
* on interfaces of other objects that may be local to your process and
* need to do permission checks on the calls coming into them (so they
* will check the permission of your own local process, and not whatever
* process originally called you).
*
* @return Returns an opaque token that can be used to restore the
* original calling identity by passing it to
* {@link #restoreCallingIdentity(long)}.
*
* @see #getCallingPid()
* @see #getCallingUid()
* @see #restoreCallingIdentity(long)
*/
public static final native long clearCallingIdentity();
/**
* Restore the identity of the incoming IPC on the current thread
* back to a previously identity that was returned by {@link
* #clearCallingIdentity}.
*
* @param token The opaque token that was previously returned by
* {@link #clearCallingIdentity}.
*
* @see #clearCallingIdentity
*/
public static final native void restoreCallingIdentity(long token);

这两个方法都是在native层实现的,不过我们可以从注释中看出一些大概的东西:

  1. clearCallingIdentity会在当前线程中重置到来的IPC标识,在将要处理调用时,调用者可能会需要调用本进程其他对象的接口。这些对象都需要再调用上做权限检查(因此它们检查当前进程的权限,而不是其他发起IPC调用的进程)。
  2. restoreCallingIdentity会在当前线程恢复先前被clearCallingIdentity清理的IPC标记。

看了上面的注释可能还不是很明白,其他说白了就是,大概意思就是:
如果A进程和B进程进行IPC调用,当A的binder线程通过IPC调用到B的binder线程中时(B线程会保存A的IPC标记,实际就是IPCThread中的mCallingPid和mCallingUid),然后如果此时B的这个binder线程要使用Binder访问本线程中的其他组件或者对象,如果这些组件或者对象需要检查访问者的IPC标记(即pid和uid),那么此时需要将B线程保存的Binder IPC标记修改为自身进程的IPC标记,这样组件或对象针对的是B进行权限检查(因为是在B中通过Binder调用它,而不是A)。这时候就需要通过clearCallingIdentity将Binder线程的IPC标记设置为当前进程的pid和uid,并将调用者A的pid和uid暂时保存起来,当B调用完后再通过restoreCallingIdentity恢复即可。

大概意思如果还不是特别明白,下面我们接着看看源码,说不定会对你有所启发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//frameworks/base/core/jni/android_util_Binder.cpp
static jlong android_os_Binder_clearCallingIdentity(JNIEnv* env, jobject clazz)
{
return IPCThreadState::self()->clearCallingIdentity();
}

static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz, jlong token)
{
// XXX temporary sanity check to debug crashes.
int uid = (int)(token>>32);
if (uid > 0 && uid < 999) {
// In Android currently there are no uids in this range.
char buf[128];
sprintf(buf, "Restoring bad calling ident: 0x%Lx", token);
jniThrowException(env, "java/lang/IllegalStateException", buf);
return;
}
IPCThreadState::self()->restoreCallingIdentity(token);
}

clearCallingIdentify和restoreCallingIdentify都依赖于IPCThreadState的实现。了解Binder的同学都知道,IPCThreadState负责和Binder驱动进行通信,它是线程单例的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class IPCThreadState
{
...
int getCallingPid() const;
int getCallingUid() const;

...
int64_t clearCallingIdentity();
void restoreCallingIdentity(int64_t token);
...

pid_t mCallingPid;
uid_t mCallingUid;
...
};

IPCThreadState有两个成员mCallingPid和mCallingUid,它们代表着Binder调用的进程pid和uid。可以通过getCallingPid和getCallingUid获取到它们的值。那么这两个值是怎么赋值的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
status_t IPCThreadState::executeCommand(int32_t cmd)
{
switch (cmd) {
...
case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));//将数据读取到tr中
...
const pid_t origPid = mCallingPid;
const uid_t origUid = mCallingUid;

mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
...

if (tr.target.ptr) {
sp<BBinder> b((BBinder*)tr.cookie);
const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
}
...

mCallingPid = origPid;
mCallingUid = origUid;
...
}
}
}

在IPCThreadState::executeCommand中,Binder进程会从读取驱动中的内容(从BR_XXX可以看出来当前Binder线程负责读驱动),数据被读取到binder_transaction_data,这是Binder IPC中最常见的情况,大多数IPC调用都走BC_TRANSACTION和BR_TRANSACTION,这里我们可以看到取到的数据是被BBinder的transact拿去处理了,现在我们先不管读到的内容是如何被BBinder处理的,我们看到在调用前后,会将mCallingPid和mCalingUid暂时保存在origPid和origUid,然后将tr中的sender_pid和sender_euid分别赋值给mCallingPid和mCallingUid,sender_pid和sender_euid分别代表了发送该数据的进程pid和uid也可以理解为发起Binder请求的进程pid和uid,它们都是由Binder驱动负责填入的。在Binder被调用端执行完后,最后将其恢复到之前的调用进程的pid和uid。在BBinder执行期间,它可以通过Binder的getCallingPid和getCallingUid来取到调用者的pid和uid,也就是这里的mCallingPid和mCallingUid,以此完成可能需要的权限检查。

知道了mCallingPid和mCallingUid怎么来的,那么我们看看IPCThreadState的clearCallingIdentity和restoreCallingIdentity会做些什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int64_t IPCThreadState::clearCallingIdentity()
{
int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid;
clearCaller();
return token;
}
void IPCThreadState::clearCaller()
{
mCallingPid = getpid();
mCallingUid = getuid();
}
void IPCThreadState::restoreCallingIdentity(int64_t token)
{
mCallingUid = (int)(token>>32);
mCallingPid = (int)token;
}

clearCallingIdentify实际上是将调用进程的uid保存在了int64_t的高32位中,低32位保存的是pid,这个值作为token返回。其中调用了clearCaller获取当前进程的pid和uid并保存在mCallingPid和mCallingUid中。而restoreCallingIdentity做了相反的操作从token中取到之前保存的mCallingUid和mCallingPid。

总结

Binder的权限控制并没有想象中那么复杂, 事实上,它只是为Binder Server提供了权限检查的凭证(即pid和uid),而不能说所有的权限工作都会由Binder负责处理,这也是不现实的,每个Binder的进程对于资源访问的权限控制策略都会不尽相同,这个是Binder没法去做控制的是不应该控制的,Binder能提供的仅仅是为权限检查的进程提供这个凭证,不过,从大的方面来讲,这也确实是一种权限控制策略。

坚持原创技术分享,您的支持将鼓励我继续创作!